/*
 * EmisorAritmetico.cpp
 *
 *  Created on: 06/04/2012
 *      Author: lucas
 */

#include "../hdr/EmisorAritmetico.h"
#include <iostream>
#include <stdio.h>
#include "../hdr/FuncionesAritmeticas.h"

using namespace std;

unsigned int EmisorAritmetico::tamanioBuffer = 1024;
unsigned int EmisorAritmetico::precision = 16;

EmisorAritmetico::EmisorAritmetico(FILE* archivo) :
		archivo(archivo), piso(0), techo((1 << precision) - 1), limite(
				1 << (precision - 1)), underflow(0), subBuffer(0), contadorSubBuffer(
				0), contadorBuffer(0) {
	buffer = new unsigned char[tamanioBuffer];
	proximoCaracter = buffer;
	archivoDebug = fopen("debug.dbg", "wb");
}

EmisorAritmetico::~EmisorAritmetico() {
	fclose(archivoDebug);
	delete[] buffer;
}

void EmisorAritmetico::imprimirNumero(unsigned int n) {
	for (int i = 8 * sizeof(unsigned int) - 1; i >= 0; i--) {
		cout << ((n & (1 << i)) >> i);
		if (i % 4 == 0)
			cout << " ";
	}
	n |= subBuffer << precision;
	cout << (n / (1.0 * ((1 << (precision + contadorSubBuffer)) + 1))) << endl;
}

void EmisorAritmetico::renormalizar() {
	unsigned int bitsEmitidos = 0;
	unsigned int emision = 0;
	// Bits en común
	emitirBits();
	// Actualizar underflow
	underflow += FuncionesAritmeticas::actualizarUnderflow(piso, techo, precision);
	//this->actualizarBuffer(emision, bitsEmitidos);
//	cout << "Renormaliza: (" << bitsEmitidos << " bytes) ";
//	 cout << "(underflow = " << underflow << ")\n";
//	 cout << "Emision:\t";
//	 this->imprimirNumero(emision);
//	 cout << "Piso:\t\t";
//	 this->imprimirNumero(this->piso);
//	 cout << "Techo:\t\t";
//	 this->imprimirNumero(this->techo);
//	 cout << contadorSubBuffer << "Buffer:\t\t";
//	 this->imprimirNumero(this->subBuffer);
//	 cout << endl;
}

void EmisorAritmetico::finalizarEmision() {
	this->actualizarBuffer(1, 1);
	if (contadorSubBuffer > 0) {
		*proximoCaracter = (unsigned char) (subBuffer
				<< (8 * sizeof(char) - contadorSubBuffer));
		contadorSubBuffer = 0;
		contadorBuffer++;
//		fprintf(stdout, "%.2x  ", *proximoCaracter);
	}
	this->escribirBufferEnArchivo();
}

void EmisorAritmetico::emitirSimbolo(unsigned short piso, unsigned short largo,
		unsigned short total) {
	fwrite(&this->piso, sizeof(unsigned int), 1, archivoDebug);
	fwrite(&this->techo, sizeof(unsigned int), 1, archivoDebug);
	unsigned int largo_actual = this->techo - this->piso + 1;
	this->techo = this->piso + (largo_actual * (piso + largo)) / total - 1;
	this->piso += (largo_actual * piso) / total;
//	cout << contadorSubBuffer << "Piso:\t\t";
//	 this->imprimirNumero(this->piso);
//	 cout << "Techo:\t\t";
//	 this->imprimirNumero(this->techo);
	this->renormalizar();
}

void EmisorAritmetico::actualizarBuffer(unsigned int bitsAEmitir,
		unsigned int cantidad) {
	subBuffer <<= cantidad;
	subBuffer |= bitsAEmitir;
	contadorSubBuffer += cantidad;
	while (contadorSubBuffer >= 8 * sizeof(char)) {
		*proximoCaracter = (unsigned char) (subBuffer
				>> (contadorSubBuffer - 8 * sizeof(char)));
//		fprintf(stdout, "%.2x ", *proximoCaracter);
		contadorSubBuffer -= 8 * sizeof(char);
		proximoCaracter++;
		contadorBuffer++;
		if (contadorBuffer >= tamanioBuffer) {
			this->escribirBufferEnArchivo();
		}
	}
}

int EmisorAritmetico::escribirBufferEnArchivo() {
	if (fwrite(buffer, sizeof(char), contadorBuffer, archivo) != contadorBuffer)
		return -1;
	contadorBuffer = 0;
	proximoCaracter = buffer;
	return 0;
}

unsigned int EmisorAritmetico::emitirBits() {
	unsigned int cantBitsEmitidos = 0;
	unsigned int limite = 1 << (precision - 1);
	unsigned int emision;
	if (techo < limite || piso >= limite) {
		if (underflow > 0) {
			emision = (techo < limite) ? 0 : 1;
			actualizarBuffer(emision, 1);
			while(underflow > precision){
				emision = (techo < limite) ? ((1 << precision) - 1) : 0;
				actualizarBuffer(emision, precision);
				underflow -= precision;
			}
			emision = (techo < limite) ? ((1 << underflow) - 1) : 0;
			actualizarBuffer(emision, underflow);
			underflow = 0;
			FuncionesAritmeticas::sacarPrimerBit(piso, precision, 0);
			FuncionesAritmeticas::sacarPrimerBit(techo, precision, 1);
		}
	}
	emision = 0;
	while (techo < limite || piso >= limite) {
		cantBitsEmitidos++;
		emision <<= 1;
		emision |= (techo < limite) ? 0 : 1;
		// Emitir underflow
		FuncionesAritmeticas::sacarPrimerBit(piso, precision, 0);
		FuncionesAritmeticas::sacarPrimerBit(techo, precision, 1);
	}
	actualizarBuffer(emision, cantBitsEmitidos);
	return cantBitsEmitidos;
}
